package com.myspringmvc.servlet;

import com.myspringmvc.annotation.Autowired;
import com.myspringmvc.annotation.RequestMapping;
import com.myspringmvc.annotation.RequestParam;
import com.myspringmvc.annotation.bean.Component;
import com.myspringmvc.annotation.bean.Controller;
import com.myspringmvc.annotation.bean.Service;
import com.mytomcat.annotation.WebInitParam;
import com.mytomcat.annotation.WebServlet;
import com.mytomcat.config.ServletConfig;
import com.mytomcat.servlet.MyHttpServlet;
import com.mytomcat.servlet.MyHttpServletRequest;
import com.mytomcat.servlet.MyHttpServletResponse;
import com.mytomcat.servlet.impl.ExceptionServlet;
import com.util.log.Logger;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

/**
 * \\\本质上还是一个Servlet 只是DispatcherServlet负责分发不同的请求 是MVC的关键点
 * 注解代替web.xml里的DispatcherServlet配置
 * 让web服务加载DispatcherServlet 其余Servlet让DispatcherServlet去加载
 */
@WebServlet(
        initParams = {@WebInitParam(name = "applicationContextPath", value = "application.yml")},
        urlPattern = "/*",
        loadOnStart = 1)
public class DispatcherServlet extends MyHttpServlet {
    private static com.util.log.Logger log = Logger.getLogger(DispatcherServlet.class);

    private final Properties properties = new Properties();

    /**
     * ioc容器 存放单例bean
     */
    private final Map<String, Object> ioc = new HashMap<>();

    /**
     * api->方法映射
     */
    private final Map<String, Method> handlerMapping = new HashMap<>();

    /**
     * api->控制器映射
     */
    private final Map<String, Object> controllerMap = new HashMap<>();

    @Override
    public void init(ServletConfig config) throws Exception {
        // 加载配置文件
        doLoadConfig(config.getInitParameter("applicationContextPath"));

        // 反射方式实例化相关的类, 将生成的bean放入ioc容器中
        log.info("初始ioc容器，扫描包目录: " + properties.getProperty("componentScan"));
        try {
            doScanner(properties.getProperty("componentScan"), false);
            // 加入内置的Controller
            String mvcPackage = DispatcherServlet.class.getPackage().getName();
            mvcPackage = mvcPackage.substring(0, mvcPackage.lastIndexOf("."));
            doScanner(mvcPackage, true); // 内置的Controller可以被覆盖
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 依赖注入
        doAutowired();
        log.info("ioc容器初始成功：");
        ioc.forEach((key, value) -> {
            log.info(" ------[" + key + "] " + value);
        });

        // 初始化handlerMapping 每个url对应一个Controller类里的一个带有@RequestMapping的方法
        initHandlerMapping();

        log.info(" DispatcherServlet初始完成");
    }

    @Override
    protected void doGet(MyHttpServletRequest req, MyHttpServletResponse resp) throws Exception {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(MyHttpServletRequest request, MyHttpServletResponse response) throws Exception {
        try {
            doDispatch(request, response);
        } catch (Exception exception) {
            log.error("无法处理Servlet请求：" + exception.getMessage());
            new ExceptionServlet().service(request, response);
        }
    }

    /**
     * 处理Servlet请求
     *
     * @param request
     * @param response
     * @throws Exception
     */
    private void doDispatch(MyHttpServletRequest request, MyHttpServletResponse response) throws Exception {
        if (handlerMapping.isEmpty())
            throw new RuntimeException("handlerMapping为空");

        String url = request.getServletPath().replaceAll("/+", "/"); // eg: /user//list -> /user/list
        if (!handlerMapping.containsKey(url))
            url = "/404";

        Method method = handlerMapping.get(url);
        // 方法的参数列表
        Parameter[] parameters = method.getParameters();
        // 请求的参数 如：/test/get?参数1=x&参数2=y&参数1=xx  数组表现为多个名字一样所以map的value是String[]
        Map<String, String[]> parameterMap = request.getParameterMap();
        // 保存参数值
        Object[] parameterValues = new Object[parameters.length];

        for (int i = 0; i < parameters.length; i++) {
            String paramTypeSimpleName = parameters[i].getType().getSimpleName();
            // 自动注入HttpServletRequest和HttpServletResponse
            if (MyHttpServletRequest.class.getSimpleName().equals(paramTypeSimpleName))
                parameterValues[i] = request;
            else if (MyHttpServletResponse.class.getSimpleName().equals(paramTypeSimpleName))
                parameterValues[i] = response;
            else if (String.class.getSimpleName().equals(paramTypeSimpleName)) { // String 类型
                // 注入参数值
                if (parameterMap.containsKey(parameters[i].getName()))
                    parameterValues[i] = parameterMap.get(parameters[i].getName())[0];
                else { // 找别名
                    if (parameters[i].isAnnotationPresent(RequestParam.class)) {
                        RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
                        if (!"".equals(requestParam.value()) && parameterMap.containsKey(requestParam.value()))
                            parameterValues[i] = parameterMap.get(requestParam.value())[0];
                        else if (!"".equals(requestParam.name()) && parameterMap.containsKey(requestParam.name()))
                            parameterValues[i] = parameterMap.get(requestParam.name())[0];
                    }
                }
            } else {
                parameterValues[i] = null;
            }
            // ... 还有List 对象等
        }
        // 通过反射调用对应方法
        try {
            log.info("servlet分发器：method=" + method);
            log.info("方法参数列表：" + Arrays.toString(parameters));
            log.info("注入的参数值：" + Arrays.toString(parameterValues));
            Object result = method.invoke(this.controllerMap.get(url), parameterValues);
            if (method.getReturnType() != void.class)
                response.returnJsonResponse(200, result == null ? "" : result.toString());
        } catch (Exception e) {
            e.printStackTrace();
            response.returnHtmlResponse(500, "<h1>500 Server Error!</h1>");
        }
    }

    /**
     * 加载配置
     *
     * @param location
     */
    private void doLoadConfig(String location) {
        try (InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(location);) {
            properties.load(inputStream);
            log.info(" 加载配置文件夹成功 配置文件路径为：classpath:" + location);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 扫描包下类并实例化相应的类到ioc容器 初次实例化bean
     * packageName 要扫描的包
     * canOverride 可以被覆盖
     *
     * @param packageName
     */
    private void doScanner(String packageName, boolean canOverride) throws Exception {
        // 当前类工程的class路径 如果不加“/” ，获取当前类的绝对路径
        String projectClassPath = this.getClass().getClassLoader().getResource("").toURI().getPath();
        File dir = new File(projectClassPath.substring(1), packageName.replaceAll("\\.", "/"));
        File[] files = dir.listFiles();
        if (files == null)
            return;
        for (File file : files) {
            if (file.isDirectory())
                doScanner(packageName + "." + file.getName(), canOverride);
            else {
                String className = packageName + "." + file.getName().replace(".class", "");
                Class<?> clazz = Class.forName(className);

                // 实例化bean
                if (clazz.isAnnotationPresent(Component.class)) {
                    doInstance(clazz.getSimpleName(), clazz, canOverride);
                } else if (clazz.isAnnotationPresent(Controller.class)) {
                    doInstance(clazz.getSimpleName(), clazz, canOverride);
                } else if (clazz.isAnnotationPresent(Service.class)) {  // 服务层的就配置多个映射
                    String value = clazz.getAnnotation(Service.class).value().trim();
                    if (!"".equals(value))  // 用户自定义的Bean名称 eg: @Service("serviceName")
                        doInstance(value, clazz, canOverride);
                    else
                        doInstance(clazz.getSimpleName(), clazz, canOverride);
                }
            }
        }
    }

    /**
     * 实例化bean
     *
     * @param beanName
     * @param clazz    canOverride 是否可以被覆盖
     * @throws Exception
     */
    private void doInstance(String beanName, Class<?> clazz, boolean canOverride) throws Exception {
        beanName = toLowerFirstWord(beanName);
        if (ioc.containsKey(beanName) && !canOverride) {
            throw new RuntimeException("已经定义了Bean " + beanName);
        }
        ioc.put(beanName, clazz.newInstance());
    }

    /***
     * 自动注入 处理ioc容器的相互依赖关系
     */
    private void doAutowired() {
        if (ioc.isEmpty()) {
            return;
        }
        ioc.forEach((key, bean) -> {
            Field[] fields = bean.getClass().getDeclaredFields();
            for (Field field : fields) {
                if (field.isAnnotationPresent(Autowired.class)) {
                    field.setAccessible(true);
                    Autowired autowired = field.getAnnotation(Autowired.class);
                    String beanName = autowired.value().trim();
                    if ("".equals(beanName))
                        beanName = toLowerFirstWord(field.getType().getSimpleName());
                    try {
                        field.set(bean, ioc.get(beanName));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                } else if (field.isAnnotationPresent(Resource.class)) {
                    field.setAccessible(true);
                    Resource resource = field.getAnnotation(Resource.class);
                    String beanName = resource.name().trim();
                    if ("".equals(beanName))
                        beanName = toLowerFirstWord(field.getType().getSimpleName());
                    try {
                        field.set(bean, ioc.get(beanName));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }

    /**
     * 首字母转小写 如 UserController -> userController
     *
     * @param name
     * @return
     */
    private String toLowerFirstWord(String name) {
        char[] charArray = name.toCharArray();
        if (charArray[0] >= Character.MIN_HIGH_SURROGATE)
            charArray[0] += 32;
        return new String(charArray);
    }

    /**
     * 映射api 每个api对应一个Controller和Method
     */
    private void initHandlerMapping() {
        if (ioc.isEmpty())
            return;
        try {
            for (Entry<String, Object> entry : ioc.entrySet()) {
                Class<?> clazz = entry.getValue().getClass();

                String baseApi = "";
                // 先看控制器类上有没有@RequestMapping注解
                RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
                baseApi += requestMapping != null ? requestMapping.value() : "";
                Method[] methods = clazz.getDeclaredMethods();
                for (Method method : methods) {
                    requestMapping = method.getAnnotation(RequestMapping.class);
                    if (requestMapping == null)
                        continue;

                    String api = (baseApi + "/" + requestMapping.value()).replaceAll("/+", "/"); // 拼接api
                    handlerMapping.put(api, method);
                    controllerMap.put(api, entry.getValue());
                }
            }
            log.info("api->方法映射：");
            handlerMapping.forEach((key, value) -> {
                log.info("[" + key + "] " + value);
            });
            log.info("api->控制器映射：");
            controllerMap.forEach((key, value) -> {
                log.info("[" + key + "] " + value);
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
